{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-05-15T03:05:30.213058Z",
     "start_time": "2019-05-15T02:58:08.778095Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "ename": "FileNotFoundError",
     "evalue": "[Errno 2] No such file or directory: 'D:\\\\shaojie\\\\NutsCloud\\\\Teach/trainData.pkl'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mFileNotFoundError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-3-94366084c366>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m    633\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    634\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 635\u001b[0;31m \u001b[0msaveFile\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfolderOfData\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m'/trainData.pkl'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'wb+'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    636\u001b[0m \u001b[0mpickle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrainData\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msaveFile\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    637\u001b[0m \u001b[0msaveFile\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'D:\\\\shaojie\\\\NutsCloud\\\\Teach/trainData.pkl'"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import pickle\n",
    "from statsmodels.stats.outliers_influence import variance_inflation_factor\n",
    "import statsmodels.api as sm\n",
    "from sklearn import ensemble\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.metrics import roc_auc_score\n",
    "\n",
    "#################################\n",
    "#由于数据已经经过一定的清洗了，非一手数据，所以我们忽略了一些步骤，进行变量衍生\n",
    "#   1, 读取数据，衍生初始变量   #\n",
    "'''\n",
    "Loan_Amount:总额度\n",
    "OS：未还金额\n",
    "Payment：还款金额\n",
    "Spend：使用金额\n",
    "Delq：逾期情况\n",
    "'''\n",
    "#################################\n",
    "#folderOfData = 'D:\\\\shaojie\\\\NutsCloud\\\\Teach'\n",
    "#trainData = pd.read_csv(folderOfData+'/训练集.csv',header = 0,engine ='python')\n",
    "#testData = pd.read_csv(folderOfData+'/测试集.csv',header = 0,engine ='python')\n",
    "trainData = pd.read_csv('训练集.csv',header = 0,engine ='python')\n",
    "testData = pd.read_csv('测试集.csv',header = 0,engine ='python')\n",
    "print('1 --')\n",
    "\n",
    "# 衍生逾期类型的特征的函数\n",
    "def DelqFeatures(event,window,type):\n",
    "    '''\n",
    "    :parms event 数据框\n",
    "    :parms windows 时间窗口\n",
    "    :parms type 响应事件类型\n",
    "    '''\n",
    "    current = 12\n",
    "    start = 12 - window + 1\n",
    "    #delq1、delq2、delq3为了获取window相对应的dataframe范围\n",
    "    delq1 = [event[a] for a in ['Delq1_' + str(t) for t in range(current, start - 1, -1)]]\n",
    "    delq2 = [event[a] for a in ['Delq2_' + str(t) for t in range(current, start - 1, -1)]]\n",
    "    delq3 = [event[a] for a in ['Delq3_' + str(t) for t in range(current, start - 1, -1)]]\n",
    "    if type == 'max delq':\n",
    "        if max(delq3) == 1:\n",
    "            return 3\n",
    "        elif max(delq2) == 1:\n",
    "            return 2\n",
    "        elif max(delq1) == 1:\n",
    "            return 1\n",
    "        else:\n",
    "            return 0\n",
    "    if type in ['M0 times','M1 times', 'M2 times']:\n",
    "        if type.find('M0')>-1:\n",
    "            return sum(delq1)\n",
    "        elif type.find('M1')>-1:\n",
    "            return sum(delq2)\n",
    "        else:\n",
    "            return sum(delq3)\n",
    "\n",
    "allFeatures = []\n",
    " \n",
    "'''\n",
    "逾期类型的特征在行为评分卡（预测违约行为）中，一般是非常显著的变量。\n",
    "通过设定时间窗口，可以衍生以下类型的逾期变量：\n",
    "'''\n",
    "print('2 --')\n",
    "# 考虑过去1个月，3个月，6个月，12个月\n",
    "for t in [1,3,6,12]:\n",
    "    # 1，过去t时间窗口内的最大逾期状态\n",
    "    allFeatures.append('maxDelqL'+str(t)+\"M\")\n",
    "    trainData['maxDelqL'+str(t)+\"M\"] = trainData.apply(lambda x: DelqFeatures(x,t,'max delq'),axis=1)\n",
    " \n",
    "    # 2，过去t时间窗口内的，M0,M1,M2的次数\n",
    "    allFeatures.append('M0FreqL' + str(t) + \"M\")\n",
    "    trainData['M0FreqL' + str(t) + \"M\"] = trainData.apply(lambda x: DelqFeatures(x,t,'M0 times'),axis=1)\n",
    " \n",
    "    allFeatures.append('M1FreqL' + str(t) + \"M\")\n",
    "    trainData['M1FreqL' + str(t) + \"M\"] = trainData.apply(lambda x: DelqFeatures(x, t, 'M1 times'), axis=1)\n",
    " \n",
    "    allFeatures.append('M2FreqL' + str(t) + \"M\")\n",
    "    trainData['M2FreqL' + str(t) + \"M\"] = trainData.apply(lambda x: DelqFeatures(x, t, 'M2 times'), axis=1)\n",
    "\n",
    "print('2 Done')\n",
    "\n",
    "#衍生额度使用率类型特征的函数\n",
    "def UrateFeatures(event, window, type):\n",
    "    '''\n",
    "    :parms event 数据框\n",
    "    :parms windows 时间窗口\n",
    "    :parms type 响应事件类型\n",
    "    '''\n",
    "    current = 12\n",
    "    start = 12 - window + 1\n",
    "    #获取在数据框内有效区域\n",
    "    monthlySpend = [event[a] for a in ['Spend_' + str(t) for t in range(current, start - 1, -1)]]\n",
    "    #获取授信总额度\n",
    "    limit = event['Loan_Amount']\n",
    "    #月使用率\n",
    "    monthlyUrate = [x / limit for x in monthlySpend]\n",
    "    if type == 'mean utilization rate':\n",
    "        return np.mean(monthlyUrate)\n",
    "    if type == 'max utilization rate':\n",
    "        return max(monthlyUrate)\n",
    "    #月额度使用率增加的月份\n",
    "    if type == 'increase utilization rate':\n",
    "        #val[0:-1]表示第一个元素到倒数第二个元素的切片\n",
    "        currentUrate = monthlyUrate[0:-1]\n",
    "        #val[1:]表示第二个元素到最后一个元素的切片\n",
    "        previousUrate = monthlyUrate[1:]\n",
    "        compareUrate = [int(x[0]>x[1]) for x in zip(currentUrate,previousUrate)]\n",
    "        return sum(compareUrate)\n",
    "\n",
    "print('3 --')\n",
    "'''\n",
    "额度使用率类型特征在行为评分卡模型中，通常是与违约高度相关的\n",
    "'''\n",
    "# 考虑过去1个月，3个月，6个月，12个月\n",
    "for t in [1,3,6,12]:\n",
    "    # 1，过去t时间窗口内的最大月额度使用率\n",
    "    allFeatures.append('maxUrateL' + str(t) + \"M\")\n",
    "    trainData['maxUrateL' + str(t) + \"M\"] = trainData.apply(lambda x: UrateFeatures(x,t,'max utilization rate'),axis = 1)\n",
    " \n",
    "    # 2，过去t时间窗口内的平均月额度使用率\n",
    "    allFeatures.append('avgUrateL' + str(t) + \"M\")\n",
    "    trainData['avgUrateL' + str(t) + \"M\"] = trainData.apply(lambda x: UrateFeatures(x, t, 'mean utilization rate'),axis=1)\n",
    " \n",
    "    # 3，过去t时间窗口内，月额度使用率增加的月份。该变量要求t>1\n",
    "    if t > 1:\n",
    "        allFeatures.append('increaseUrateL' + str(t) + \"M\")\n",
    "        trainData['increaseUrateL' + str(t) + \"M\"] = trainData.apply(lambda x: UrateFeatures(x, t, 'increase utilization rate'),axis=1)\n",
    "print('3 Done')\n",
    "\n",
    "#衍生还款类型特征的函数\n",
    "def PaymentFeatures(event, window, type):\n",
    "    current = 12\n",
    "    start = 12 - window + 1\n",
    "    #月还款金额\n",
    "    currentPayment = [event[a] for a in ['Payment_' + str(t) for t in range(current, start - 1, -1)]]\n",
    "    #月使用金额，错位一下\n",
    "    previousOS = [event[a] for a in ['OS_' + str(t) for t in range(current-1, start - 2, -1)]]\n",
    "    monthlyPayRatio = []\n",
    "    for Pay_OS in zip(currentPayment,previousOS):\n",
    "        #前一个月使用了才会产生还款\n",
    "        if Pay_OS[1]>0:\n",
    "            payRatio = Pay_OS[0]*1.0 / Pay_OS[1]\n",
    "            monthlyPayRatio.append(payRatio)\n",
    "        #前一个月没使用，就按照100%还款\n",
    "        else:\n",
    "            monthlyPayRatio.append(1)\n",
    "    if type == 'min payment ratio':\n",
    "        return min(monthlyPayRatio)\n",
    "    if type == 'max payment ratio':\n",
    "        return max(monthlyPayRatio)\n",
    "    if type == 'mean payment ratio':\n",
    "        total_payment = sum(currentPayment)\n",
    "        total_OS = sum(previousOS)\n",
    "        if total_OS > 0:\n",
    "            return total_payment / total_OS\n",
    "        else:\n",
    "            return 1\n",
    "print('4 --')\n",
    "'''\n",
    "还款类型特征也是行为评分卡模型中常用的特征\n",
    "'''\n",
    "# 考虑过去1个月，3个月，6个月，12个月\n",
    "for t in [1,3,6,12]:\n",
    "    # 1，过去t时间窗口内的最大月还款率\n",
    "    allFeatures.append('maxPayL' + str(t) + \"M\")\n",
    "    trainData['maxPayL' + str(t) + \"M\"] = trainData.apply(lambda x: PaymentFeatures(x, t, 'max payment ratio'),axis=1)\n",
    "    # 2，过去t时间窗口内的最小月还款率\n",
    "    allFeatures.append('minPayL' + str(t) + \"M\")\n",
    "    trainData['minPayL' + str(t) + \"M\"] = trainData.apply(lambda x: PaymentFeatures(x, t, 'min payment ratio'),axis=1)\n",
    "    # 3，过去t时间窗口内的平均月还款率\n",
    "    allFeatures.append('avgPayL' + str(t) + \"M\")\n",
    "    trainData['avgPayL' + str(t) + \"M\"] = trainData.apply(lambda x: PaymentFeatures(x, t, 'mean payment ratio'),axis=1)\n",
    "\n",
    "print('4 Done')\n",
    "\n",
    "###函数########\n",
    "#计算变量分箱之后各分箱的坏样本率\n",
    "def BinBadRate(df, col, target, grantRateIndicator=0):\n",
    "    '''\n",
    "    :param df: 需要计算好坏比率的数据集\n",
    "    :param col: 需要计算好坏比率的特征\n",
    "    :param target: 好坏标签\n",
    "    :param grantRateIndicator: 1返回总体的坏样本率，0不返回\n",
    "    :return: 每箱的坏样本率，以及总体的坏样本率（当grantRateIndicator＝＝1时）\n",
    "    '''\n",
    "    #print(df.groupby([col])[target])\n",
    "    total = df.groupby([col])[target].count()\n",
    "    #print(total)\n",
    "    total = pd.DataFrame({'total': total})\n",
    "    #print(total)\n",
    "    bad = df.groupby([col])[target].sum()\n",
    "    bad = pd.DataFrame({'bad': bad})\n",
    "    #合并\n",
    "    regroup = total.merge(bad, left_index=True, right_index=True, how='left')\n",
    "    #print(regroup)\n",
    "    regroup.reset_index(level=0, inplace=True)\n",
    "    #print(regroup)\n",
    "    #计算坏样本率\n",
    "    regroup['bad_rate'] = regroup.apply(lambda x: x.bad * 1.0 / x.total, axis=1)\n",
    "    #print(regroup)\n",
    "    #生成字典，（变量名取值：坏样本率）\n",
    "    dicts = dict(zip(regroup[col],regroup['bad_rate']))\n",
    "    if grantRateIndicator==0:\n",
    "        return (dicts, regroup)\n",
    "    N = sum(regroup['total'])\n",
    "    B = sum(regroup['bad'])\n",
    "    #总体样本率\n",
    "    overallRate = B * 1.0 / N\n",
    "    return (dicts, regroup, overallRate)\n",
    "\n",
    "\n",
    "## 判断某变量的坏样本率是否单调\n",
    "def BadRateMonotone(df, sortByVar, target,special_attribute = []):\n",
    "    '''\n",
    "    :param df: 包含检验坏样本率的变量，和目标变量\n",
    "    :param sortByVar: 需要检验坏样本率的变量\n",
    "    :param target: 目标变量，0、1表示好、坏\n",
    "    :param special_attribute: 不参与检验的特殊值\n",
    "    :return: 坏样本率单调与否\n",
    "    '''\n",
    "    df2 = df.loc[~df[sortByVar].isin(special_attribute)]\n",
    "    if len(set(df2[sortByVar])) <= 2:\n",
    "        return True\n",
    "    regroup = BinBadRate(df2, sortByVar, target)[1]\n",
    "    combined = zip(regroup['total'],regroup['bad'])\n",
    "    badRate = [x[1]*1.0/x[0] for x in combined]\n",
    "    badRateNotMonotone = [badRate[i]<badRate[i+1] and badRate[i] < badRate[i-1] or badRate[i]>badRate[i+1] and badRate[i] > badRate[i-1]\n",
    "                          for i in range(1,len(badRate)-1)]\n",
    "    if True in badRateNotMonotone:\n",
    "        return False\n",
    "    else:\n",
    "        return True\n",
    "\n",
    "print('5 --')\n",
    "############################\n",
    "#   2, 分箱，计算WOE并编码   #\n",
    "############################\n",
    "\n",
    "'''\n",
    "对类别型变量的分箱和WOE计算\n",
    "可以通过计算取值个数的方式判断是否是类别型变量\n",
    "'''\n",
    "#类别型变量\n",
    "categoricalFeatures = []\n",
    "#连续型变量\n",
    "numericalFeatures = []\n",
    "WOE_IV_dict = {}\n",
    "for var in allFeatures:\n",
    "    if len(set(trainData[var])) > 5:\n",
    "        numericalFeatures.append(var)\n",
    "    else:\n",
    "        categoricalFeatures.append(var)\n",
    "\n",
    "not_monotone =[]\n",
    "for var in categoricalFeatures:\n",
    "    #检查bad rate在箱中的单调性\n",
    "    if not BadRateMonotone(trainData, var, 'label'):\n",
    "        not_monotone.append(var)\n",
    "\n",
    "#print(\"数值取值小于5类别型变量{}坏样本率不单调\".format(not_monotone))\n",
    " \n",
    "# 'M1FreqL3M'，'M2FreqL3M', 'maxDelqL12M' 是不单调的，需要合并其中某些类别\n",
    "trainData.groupby(['M2FreqL3M'])['label'].mean()  #检查单调性\n",
    "trainData.groupby(['M2FreqL3M'])['label'].count()   #其中，M2FreqL3M＝3总共只有3个样本，因此要进行合并\n",
    " \n",
    " \n",
    " \n",
    "# 将 M2FreqL3M>=1的合并为一组，计算WOE和IV\n",
    "trainData['M2FreqL3M_Bin'] = trainData['M2FreqL3M'].apply(lambda x: int(x>=1))\n",
    "trainData.groupby(['M2FreqL3M_Bin'])['label'].mean()\n",
    "\n",
    "print('5 Done')\n",
    " \n",
    "#计算WOE值\n",
    "def CalcWOE(df, col, target):\n",
    "    '''\n",
    "    :param df: 包含需要计算WOE的变量和目标变量\n",
    "    :param col: 需要计算WOE、IV的变量，必须是分箱后的变量，或者不需要分箱的类别型变量\n",
    "    :param target: 目标变量，0、1表示好、坏\n",
    "    :return: 返回WOE和IV\n",
    "    '''\n",
    "    total = df.groupby([col])[target].count()\n",
    "    total = pd.DataFrame({'total': total})\n",
    "    bad = df.groupby([col])[target].sum()\n",
    "    bad = pd.DataFrame({'bad': bad})\n",
    "    regroup = total.merge(bad, left_index=True, right_index=True, how='left')\n",
    "    regroup.reset_index(level=0, inplace=True)\n",
    "    N = sum(regroup['total'])\n",
    "    B = sum(regroup['bad'])\n",
    "    regroup['good'] = regroup['total'] - regroup['bad']\n",
    "    G = N - B\n",
    "    regroup['bad_pcnt'] = regroup['bad'].map(lambda x: x*1.0/B)\n",
    "    regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)\n",
    "    regroup['WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)\n",
    "    WOE_dict = regroup[[col,'WOE']].set_index(col).to_dict(orient='index')\n",
    "    for k, v in WOE_dict.items():\n",
    "        WOE_dict[k] = v['WOE']\n",
    "    IV = regroup.apply(lambda x: (x.good_pcnt-x.bad_pcnt)*np.log(x.good_pcnt*1.0/x.bad_pcnt),axis = 1)\n",
    "    IV = sum(IV)\n",
    "    return {\"WOE\": WOE_dict, 'IV':IV}\n",
    "\n",
    "print('6 --')\n",
    "WOE_IV_dict['M2FreqL3M_Bin'] = CalcWOE(trainData, 'M2FreqL3M_Bin', 'label')\n",
    " \n",
    "trainData.groupby(['M1FreqL3M'])['label'].mean()  #检查单调性\n",
    "trainData.groupby(['M1FreqL3M'])['label'].count()\n",
    " \n",
    "# 除了M1FreqL3M＝3外， 其他组别的bad rate单调。\n",
    "# 此外，M1FreqL3M＝0 占比很大，因此将M1FreqL3M>=1的分为一组\n",
    "trainData['M1FreqL3M_Bin'] = trainData['M1FreqL3M'].apply(lambda x: int(x>=1))\n",
    "trainData.groupby(['M1FreqL3M_Bin'])['label'].mean()\n",
    "WOE_IV_dict['M1FreqL3M_Bin'] = CalcWOE(trainData, 'M1FreqL3M_Bin', 'label')\n",
    "\n",
    "'''\n",
    "对其他单调的类别型变量，检查是否有一箱的占比低于5%。 如果有，将该变量进行合并\n",
    "'''\n",
    "small_bin_var = []\n",
    "large_bin_var = []\n",
    "N = trainData.shape[0]\n",
    "for var in categoricalFeatures:\n",
    "    if var not in not_monotone:\n",
    "        total = trainData.groupby([var])[var].count()\n",
    "        pcnt = total * 1.0 / N\n",
    "        if min(pcnt)<0.05:\n",
    "            small_bin_var.append({var:pcnt.to_dict()})\n",
    "        else:\n",
    "            large_bin_var.append(var)\n",
    " \n",
    " \n",
    "#对于M2FreqL1M、M2FreqL6M和M2FreqL12M，由于有部分箱占了很大比例，故删除，因为样本表现99%都一样，这个变量没有区分度\n",
    "allFeatures.remove('M2FreqL1M')\n",
    "allFeatures.remove('M2FreqL6M')\n",
    "allFeatures.remove('M2FreqL12M')\n",
    "\n",
    "print('6 Done')\n",
    "\n",
    "def MergeByCondition(x,condition_list):\n",
    "    #condition_list是条件列表。满足第几个condition，就输出几\n",
    "    s = 0\n",
    "    for condition in condition_list:\n",
    "        if eval(str(x)+condition):\n",
    "            return s\n",
    "        else:\n",
    "            s+=1\n",
    "    return s\n",
    "\n",
    "print('7 --')\n",
    "\n",
    "#对于small_bin_var中的其他变量，将最小的箱和相邻的箱进行合并并计算WOE\n",
    "trainData['maxDelqL1M_Bin'] = trainData['maxDelqL1M'].apply(lambda x: MergeByCondition(x,['==0','==1','>=2']))\n",
    "trainData['maxDelqL3M_Bin'] = trainData['maxDelqL3M'].apply(lambda x: MergeByCondition(x,['==0','==1','>=2']))\n",
    "trainData['maxDelqL6M_Bin'] = trainData['maxDelqL6M'].apply(lambda x: MergeByCondition(x,['==0','==1','>=2']))\n",
    "for var in ['maxDelqL1M_Bin','maxDelqL3M_Bin','maxDelqL6M_Bin']:\n",
    "    WOE_IV_dict[var] = CalcWOE(trainData, var, 'label')\n",
    " \n",
    " \n",
    "'''\n",
    "对于不需要合并、原始箱的bad rate单调的特征，直接计算WOE和IV\n",
    "'''\n",
    "for var in large_bin_var:\n",
    "    WOE_IV_dict[var] = CalcWOE(trainData, var, 'label')\n",
    "\n",
    "print('7 Done')\n",
    "\n",
    "def AssignBin(x, cutOffPoints,special_attribute=[]):\n",
    "    '''\n",
    "    :param x: 某个变量的某个取值\n",
    "    :param cutOffPoints: 上述变量的分箱结果，用切分点表示\n",
    "    :param special_attribute:  不参与分箱的特殊取值\n",
    "    :return: 分箱后的对应的第几个箱，从0开始\n",
    "    for example, if cutOffPoints = [10,20,30], if x = 7, return Bin 0. If x = 35, return Bin 3\n",
    "    '''\n",
    "    numBin = len(cutOffPoints) + 1 + len(special_attribute)\n",
    "    if x in special_attribute:\n",
    "        i = special_attribute.index(x)+1\n",
    "        return 'Bin {}'.format(0-i)\n",
    "    if x<=cutOffPoints[0]:\n",
    "        return 'Bin 0'\n",
    "    elif x > cutOffPoints[-1]:\n",
    "        return 'Bin {}'.format(numBin-1)\n",
    "    else:\n",
    "        for i in range(0,numBin-1):\n",
    "            if cutOffPoints[i] < x <=  cutOffPoints[i+1]:\n",
    "                return 'Bin {}'.format(i+1)\n",
    "\n",
    "\n",
    "\n",
    "def AssignGroup(x, bin):\n",
    "    '''\n",
    "    :param x: 某个变量的某个取值\n",
    "    :param bin: 上述变量的分箱结果\n",
    "    :return: x在分箱结果下的映射\n",
    "    '''\n",
    "    N = len(bin)\n",
    "    if x<=min(bin):\n",
    "        return min(bin)\n",
    "    elif x>max(bin):\n",
    "        return 10e10\n",
    "    else:\n",
    "        for i in range(N-1):\n",
    "            if bin[i] < x <= bin[i+1]:\n",
    "                return bin[i+1]\n",
    " \n",
    " \n",
    "def SplitData(df, col, numOfSplit, special_attribute=[]):\n",
    "    '''\n",
    "    :param df: 按照col排序后的数据集\n",
    "    :param col: 待分箱的变量\n",
    "    :param numOfSplit: 切分的组别数\n",
    "    :param special_attribute: 在切分数据集的时候，某些特殊值需要排除在外\n",
    "    :return: 在原数据集上增加一列，把原始细粒度的col重新划分成粗粒度的值，便于分箱中的合并处理\n",
    "    '''\n",
    "    df2 = df.copy()\n",
    "    if special_attribute != []:\n",
    "        df2 = df.loc[~df[col].isin(special_attribute)]\n",
    "    N = df2.shape[0]#行数\n",
    "    #\" / \"就表示 浮点数除法，返回浮点结果;\" // \"表示整数除法\n",
    "    n = N//numOfSplit #每组样本数\n",
    "    splitPointIndex = [i*n for i in range(1,numOfSplit)] #分割点的下标\n",
    "    '''\n",
    "    [i*2 for i in range(1,100)]\n",
    "    [2, 4, 6, 8, 10,......,198]\n",
    "    '''\n",
    "    rawValues = sorted(list(df2[col])) #对取值进行排序\n",
    "    #取到粗糙卡方划分节点\n",
    "    splitPoint = [rawValues[i] for i in splitPointIndex] #分割点的取值\n",
    "    splitPoint = sorted(list(set(splitPoint)))\n",
    "    return splitPoint\n",
    "\n",
    "\n",
    "#计算卡方值的函数\n",
    "def Chi2(df, total_col, bad_col, overallRate):\n",
    "    '''\n",
    "    :param df: 包含全部样本总计与坏样本总计的数据框\n",
    "    :param total_col: 全部样本的个数\n",
    "    :param bad_col: 坏样本的个数\n",
    "    :param overallRate: 全体样本的坏样本占比\n",
    "    :return: 卡方值\n",
    "    '''\n",
    "    df2 = df.copy()\n",
    "    # 期望坏样本个数＝全部样本个数*平均坏样本占比\n",
    "    df2['expected'] = df[total_col].apply(lambda x: x*overallRate)\n",
    "    combined = zip(df2['expected'], df2[bad_col])\n",
    "    chi = [(i[0]-i[1])**2/i[0] for i in combined]\n",
    "    chi2 = sum(chi)\n",
    "    return chi2\n",
    "\n",
    "\n",
    "##ChiMerge_MaxInterval：通过指定最大间隔数，使用卡方值分割连续变量\n",
    "def ChiMerge(df, col, target, max_interval=5,special_attribute=[],minBinPcnt=0):\n",
    "    '''\n",
    "    :param df: 包含目标变量与分箱属性的数据框\n",
    "    :param col: 需要分箱的属性\n",
    "    :param target: 目标变量，取值0或1\n",
    "    :param max_interval: 最大分箱数。如果原始属性的取值个数低于该参数，不执行这段函数\n",
    "    :param special_attribute: 不参与分箱的属性取值，缺失值的情况\n",
    "    :param minBinPcnt：最小箱的占比，默认为0\n",
    "    :return: 分箱结果\n",
    "    '''\n",
    "    colLevels = sorted(list(set(df[col])))\n",
    "    N_distinct = len(colLevels)#不同的取值个数\n",
    "    if N_distinct <= max_interval:  #如果原始属性的取值个数低于max_interval，不执行这段函数\n",
    "        print (\"原始属性{}的取值个数低于max_interval\".format(col))\n",
    "        #分箱分数间隔段，少一个值也可以\n",
    "        #返回值colLevels会少一个最大值\n",
    "        return colLevels[:-1]\n",
    "    else:\n",
    "        if len(special_attribute)>=1:\n",
    "            #df1数据框取trainData中col那一列为特殊值的数据集\n",
    "            #df1 = df.loc[df[col].isin(special_attribute)]\n",
    "            print('{} 有缺失值的情况'.format(col))\n",
    "            #用逆函数对筛选后的结果取余，起删除指定行作用\n",
    "            df2 = df.loc[~df[col].isin(special_attribute)]\n",
    "        else:\n",
    "            df2 = df.copy()\n",
    "        N_distinct = len(list(set(df2[col])))#该特征不同的取值\n",
    " \n",
    "        # 步骤一: 通过col对数据集进行分组，求出每组的总样本数与坏样本数\n",
    "        if N_distinct > 100:\n",
    "            '''\n",
    "            split_x样例\n",
    "            [2, 8, 9.3 , 1 0, 30 ,......,1800]\n",
    "            '''\n",
    "            split_x = SplitData(df2, col, 100)\n",
    "            #把值变为划分点的值\n",
    "            df2['temp'] = df2[col].map(lambda x: AssignGroup(x, split_x))\n",
    "        else:\n",
    "            #假如数值取值小于100就不发生变化了\n",
    "            df2['temp'] = df2[col]\n",
    "        # 总体bad rate将被用来计算expected bad count\n",
    "        (binBadRate, regroup, overallRate) = BinBadRate(df2, 'temp', target, grantRateIndicator=1)\n",
    " \n",
    "        # 首先，每个单独的属性值将被分为单独的一组\n",
    "        # 对属性值进行排序，然后两两组别进行合并\n",
    "        colLevels = sorted(list(set(df2['temp'])))\n",
    "        groupIntervals = [[i] for i in colLevels]\n",
    " \n",
    "        # 步骤二：建立循环，不断合并最优的相邻两个组别，直到：\n",
    "        # 1，最终分裂出来的分箱数<＝预设的最大分箱数\n",
    "        # 2，每箱的占比不低于预设值（可选）\n",
    "        # 3，每箱同时包含好坏样本\n",
    "        # 如果有特殊属性，那么最终分裂出来的分箱数＝预设的最大分箱数－特殊属性的个数\n",
    "        split_intervals = max_interval - len(special_attribute)\n",
    "        while (len(groupIntervals) > split_intervals):  # 终止条件: 当前分箱数＝预设的分箱数\n",
    "            # 每次循环时, 计算合并相邻组别后的卡方值。具有最小卡方值的合并方案，是最优方案\n",
    "            #存储卡方值\n",
    "            chisqList = []\n",
    "            for k in range(len(groupIntervals)-1):\n",
    "                temp_group = groupIntervals[k] + groupIntervals[k+1]\n",
    "                df2b = regroup.loc[regroup['temp'].isin(temp_group)]\n",
    "                chisq = Chi2(df2b, 'total', 'bad', overallRate)\n",
    "                chisqList.append(chisq)\n",
    "            best_comnbined = chisqList.index(min(chisqList))\n",
    "            groupIntervals[best_comnbined] = groupIntervals[best_comnbined] + groupIntervals[best_comnbined+1]\n",
    "            # after combining two intervals, we need to remove one of them\n",
    "            groupIntervals.remove(groupIntervals[best_comnbined+1])\n",
    "        groupIntervals = [sorted(i) for i in groupIntervals]\n",
    "        cutOffPoints = [max(i) for i in groupIntervals[:-1]]\n",
    " \n",
    "        # 检查是否有箱没有好或者坏样本。如果有，需要跟相邻的箱进行合并，直到每箱同时包含好坏样本\n",
    "        groupedvalues = df2['temp'].apply(lambda x: AssignBin(x, cutOffPoints))\n",
    "        #已成完成卡方分箱，但是没有考虑其单调性\n",
    "        df2['temp_Bin'] = groupedvalues\n",
    "        (binBadRate,regroup) = BinBadRate(df2, 'temp_Bin', target)\n",
    "        [minBadRate, maxBadRate] = [min(binBadRate.values()),max(binBadRate.values())]\n",
    "        while minBadRate ==0 or maxBadRate == 1:\n",
    "            # 找出全部为好／坏样本的箱\n",
    "            indexForBad01 = regroup[regroup['bad_rate'].isin([0,1])].temp_Bin.tolist()\n",
    "            bin=indexForBad01[0]\n",
    "            # 如果是最后一箱，则需要和上一个箱进行合并，也就意味着分裂点cutOffPoints中的最后一个需要移除\n",
    "            if bin == max(regroup.temp_Bin):\n",
    "                cutOffPoints = cutOffPoints[:-1]\n",
    "            # 如果是第一箱，则需要和下一个箱进行合并，也就意味着分裂点cutOffPoints中的第一个需要移除\n",
    "            elif bin == min(regroup.temp_Bin):\n",
    "                cutOffPoints = cutOffPoints[1:]\n",
    "            # 如果是中间的某一箱，则需要和前后中的一个箱进行合并，依据是较小的卡方值\n",
    "            else:\n",
    "                # 和前一箱进行合并，并且计算卡方值\n",
    "                currentIndex = list(regroup.temp_Bin).index(bin)\n",
    "                prevIndex = list(regroup.temp_Bin)[currentIndex - 1]\n",
    "                df3 = df2.loc[df2['temp_Bin'].isin([prevIndex, bin])]\n",
    "                (binBadRate, df2b) = BinBadRate(df3, 'temp_Bin', target)\n",
    "                chisq1 = Chi2(df2b, 'total', 'bad', overallRate)\n",
    "                # 和后一箱进行合并，并且计算卡方值\n",
    "                laterIndex = list(regroup.temp_Bin)[currentIndex + 1]\n",
    "                df3b = df2.loc[df2['temp_Bin'].isin([laterIndex, bin])]\n",
    "                (binBadRate, df2b) = BinBadRate(df3b, 'temp_Bin', target)\n",
    "                chisq2 = Chi2(df2b, 'total', 'bad', overallRate)\n",
    "                if chisq1 < chisq2:\n",
    "                    cutOffPoints.remove(cutOffPoints[currentIndex - 1])\n",
    "                else:\n",
    "                    cutOffPoints.remove(cutOffPoints[currentIndex])\n",
    "            # 完成合并之后，需要再次计算新的分箱准则下，每箱是否同时包含好坏样本\n",
    "            groupedvalues = df2['temp'].apply(lambda x: AssignBin(x, cutOffPoints))\n",
    "            df2['temp_Bin'] = groupedvalues\n",
    "            (binBadRate, regroup) = BinBadRate(df2, 'temp_Bin', target)\n",
    "            [minBadRate, maxBadRate] = [min(binBadRate.values()), max(binBadRate.values())]\n",
    " \n",
    "        # 需要检查分箱后的最小占比\n",
    "        if minBinPcnt > 0:\n",
    "            groupedvalues = df2['temp'].apply(lambda x: AssignBin(x, cutOffPoints))\n",
    "            df2['temp_Bin'] = groupedvalues\n",
    "            #value_counts每个数值出现了多少次\n",
    "            valueCounts = groupedvalues.value_counts().to_frame()\n",
    "            N=sum(valueCounts['temp'])\n",
    "            valueCounts['pcnt'] = valueCounts['temp'].apply(lambda x: x * 1.0 / N)\n",
    "            valueCounts = valueCounts.sort_index()\n",
    "            minPcnt = min(valueCounts['pcnt'])\n",
    "            #一定要箱数大于2才可以，要不就不能再合并了\n",
    "            while minPcnt < minBinPcnt and len(cutOffPoints) > 2:\n",
    "                # 找出占比最小的箱\n",
    "                indexForMinPcnt = valueCounts[valueCounts['pcnt'] == minPcnt].index.tolist()[0]\n",
    "                # 如果占比最小的箱是最后一箱，则需要和上一个箱进行合并，也就意味着分裂点cutOffPoints中的最后一个需要移除\n",
    "                if indexForMinPcnt == max(valueCounts.index):\n",
    "                    cutOffPoints = cutOffPoints[:-1]\n",
    "                # 如果占比最小的箱是第一箱，则需要和下一个箱进行合并，也就意味着分裂点cutOffPoints中的第一个需要移除\n",
    "                elif indexForMinPcnt == min(valueCounts.index):\n",
    "                    cutOffPoints = cutOffPoints[1:]\n",
    "                # 如果占比最小的箱是中间的某一箱，则需要和前后中的一个箱进行合并，依据是较小的卡方值\n",
    "                else:\n",
    "                    # 和前一箱进行合并，并且计算卡方值\n",
    "                    currentIndex = list(valueCounts.index).index(indexForMinPcnt)\n",
    "                    prevIndex = list(valueCounts.index)[currentIndex - 1]\n",
    "                    df3 = df2.loc[df2['temp_Bin'].isin([prevIndex, indexForMinPcnt])]\n",
    "                    (binBadRate, df2b) = BinBadRate(df3, 'temp_Bin', target)\n",
    "                    chisq1 = Chi2(df2b, 'total', 'bad', overallRate)\n",
    "                    # 和后一箱进行合并，并且计算卡方值\n",
    "                    laterIndex = list(valueCounts.index)[currentIndex + 1]\n",
    "                    df3b = df2.loc[df2['temp_Bin'].isin([laterIndex, indexForMinPcnt])]\n",
    "                    (binBadRate, df2b) = BinBadRate(df3b, 'temp_Bin', target)\n",
    "                    chisq2 = Chi2(df2b, 'total', 'bad', overallRate)\n",
    "                    if chisq1 < chisq2:\n",
    "                        cutOffPoints.remove(cutOffPoints[currentIndex - 1])\n",
    "                    else:\n",
    "                        cutOffPoints.remove(cutOffPoints[currentIndex])\n",
    "        cutOffPoints = special_attribute + cutOffPoints\n",
    "        return cutOffPoints\n",
    "\n",
    "\n",
    "'''\n",
    "对于数值型变量，需要先分箱，再计算WOE、IV\n",
    "分箱的结果需要满足：\n",
    "1，箱数不超过5\n",
    "2，bad rate单调\n",
    "3，每箱占比不低于5%\n",
    "'''\n",
    "bin_dict = []\n",
    "for var in numericalFeatures:\n",
    "    binNum = 5\n",
    "    newBin = var + '_Bin'\n",
    "    bin = ChiMerge(trainData, var, 'label',max_interval=binNum,minBinPcnt = 0.05)\n",
    "    trainData[newBin] = trainData[var].apply(lambda x: AssignBin(x,bin))\n",
    "    # 如果不满足单调性，就降低分箱个数\n",
    "    while not BadRateMonotone(trainData, newBin, 'label'):\n",
    "        binNum -= 1\n",
    "        bin = ChiMerge(trainData, var, 'label', max_interval=binNum, minBinPcnt=0.05)\n",
    "        trainData[newBin] = trainData[var].apply(lambda x: AssignBin(x, bin))\n",
    "    WOE_IV_dict[newBin] = CalcWOE(trainData, newBin, 'label')\n",
    "    bin_dict.append({var:bin})\n",
    "\n",
    "\n",
    "\n",
    "##############################\n",
    "#   3, 单变量分析和多变量分析   #\n",
    "##############################\n",
    "#  选取IV高于0.02的变量\n",
    "high_IV = [(k,v['IV']) for k,v in WOE_IV_dict.items() if v['IV'] >= 0.02]\n",
    "high_IV_sorted = sorted(high_IV, key=lambda k: k[1],reverse=True)\n",
    "IV_values = [i[1] for i in high_IV_sorted]\n",
    "IV_name = [i[0] for i in high_IV_sorted]\n",
    "plt.title('High feature IV')\n",
    "plt.bar(range(len(IV_values)),IV_values)\n",
    "\n",
    "\n",
    "for (var,iv) in high_IV:\n",
    "    newVar = var+\"_WOE\"\n",
    "    trainData[newVar] = trainData[var].map(lambda x: WOE_IV_dict[var]['WOE'][x])\n",
    "\n",
    "\n",
    "saveFile = open('trainData.pkl','wb+')\n",
    "pickle.dump(trainData,saveFile)\n",
    "saveFile.close()\n",
    "\n",
    "\n",
    "saveFile = open('trainData.pkl','rb+')\n",
    "trainData = pickle.load(saveFile)\n",
    "saveFile.close()\n",
    "\n",
    "\n",
    "\n",
    "'''\n",
    "多变量分析：比较两两线性相关性。如果相关系数的绝对值高于阈值，剔除IV较低的一个\n",
    "'''\n",
    "deleted_index = []\n",
    "cnt_vars = len(high_IV_sorted)\n",
    "for i in range(cnt_vars):\n",
    "    if i in deleted_index:\n",
    "        continue\n",
    "    x1 = high_IV_sorted[i][0]+\"_WOE\"\n",
    "    for j in range(cnt_vars):\n",
    "        if i == j or j in deleted_index:\n",
    "            continue\n",
    "        y1 = high_IV_sorted[j][0]+\"_WOE\"\n",
    "        roh = np.corrcoef(trainData[x1],trainData[y1])[0,1]\n",
    "        if abs(roh)>0.7:\n",
    "            x1_IV = high_IV_sorted[i][1]\n",
    "            y1_IV = high_IV_sorted[j][1]\n",
    "            if x1_IV > y1_IV:\n",
    "                deleted_index.append(j)\n",
    "            else:\n",
    "                deleted_index.append(i)\n",
    "\n",
    "single_analysis_vars = [high_IV_sorted[i][0]+\"_WOE\" for i in range(cnt_vars) if i not in deleted_index]\n",
    "\n",
    "\n",
    "X = trainData[single_analysis_vars]\n",
    "f, ax = plt.subplots(figsize=(10, 8))\n",
    "corr = X.corr()\n",
    "sns.heatmap(corr, mask=np.zeros_like(corr, dtype=np.bool), cmap=sns.diverging_palette(220, 10, as_cmap=True),square=True, ax=ax)\n",
    "\n",
    "\n",
    "\n",
    "'''\n",
    "多变量分析：VIF\n",
    "'''\n",
    "X = np.matrix(trainData[single_analysis_vars])\n",
    "VIF_list = [variance_inflation_factor(X, i) for i in range(X.shape[1])]\n",
    "print(max(VIF_list))\n",
    "# 最大的VIF是 3.429，小于10，因此这一步认为没有多重共线性\n",
    "multi_analysis = single_analysis_vars\n",
    "\n",
    "\n",
    "################################\n",
    "#   4, 建立逻辑回归模型预测违约   #\n",
    "################################\n",
    "X = trainData[multi_analysis]\n",
    "#截距项\n",
    "X['intercept'] = [1] * X.shape[0]\n",
    "y = trainData['label']\n",
    "logit = sm.Logit(y, X)\n",
    "logit_result = logit.fit()\n",
    "pvalues = logit_result.pvalues\n",
    "params = logit_result.params\n",
    "fit_result = pd.concat([params,pvalues],axis=1)\n",
    "fit_result.columns = ['coef','p-value']\n",
    "fit_result = fit_result.sort_values(by = 'coef')\n",
    "\n",
    "'''\n",
    "                               coef        p-value\n",
    "intercept                 -1.812690   0.000000e+00\n",
    "increaseUrateL6M_Bin_WOE  -1.220508   2.620858e-62\n",
    "maxDelqL3M_Bin_WOE        -0.735785  3.600473e-163\n",
    "M2FreqL3M_Bin_WOE         -0.681009   1.284840e-63\n",
    "avgUrateL1M_Bin_WOE       -0.548608   2.350785e-07\n",
    "avgUrateL3M_Bin_WOE       -0.467298   8.870679e-05\n",
    "M0FreqL3M_WOE             -0.392261   2.386403e-26\n",
    "avgUrateL6M_Bin_WOE       -0.309831   3.028939e-02\n",
    "increaseUrateL3M_WOE      -0.300805   1.713878e-03\n",
    "maxUrateL3M_Bin_WOE       -0.213742   1.412028e-01\n",
    "avgPayL6M_Bin_WOE         -0.208924   4.241600e-07\n",
    "maxDelqL1M_Bin_WOE        -0.162785   1.835990e-07\n",
    "M1FreqL12M_Bin_WOE        -0.125595   2.576692e-03\n",
    "M1FreqL6M_Bin_WOE         -0.067979   8.572653e-02\n",
    "maxPayL6M_Bin_WOE         -0.063942   3.807461e-01\n",
    "maxUrateL6M_Bin_WOE       -0.056266   7.120434e-01\n",
    "avgPayL12M_Bin_WOE        -0.039538   4.487068e-01\n",
    "maxPayL12M_Bin_WOE         0.030780   8.135143e-01\n",
    "M0FreqL12M_Bin_WOE         0.077365   1.826047e-01\n",
    "minPayL6M_Bin_WOE          0.107868   3.441998e-01\n",
    "increaseUrateL12M_Bin_WOE  0.115845   4.292397e-01\n",
    "M0FreqL6M_Bin_WOE          0.145630   1.869349e-03\n",
    "minPayL3M_Bin_WOE          0.151294   4.293344e-02\n",
    "avgPayL1M_Bin_WOE          0.260946   6.606818e-04\n",
    "变量 \n",
    "maxPayL12M_Bin_WOE         0.030780   8.135143e-01\n",
    "M0FreqL12M_Bin_WOE         0.077365   1.826047e-01\n",
    "minPayL6M_Bin_WOE          0.107868   3.441998e-01\n",
    "increaseUrateL12M_Bin_WOE  0.115845   4.292397e-01\n",
    "M0FreqL6M_Bin_WOE          0.145630   1.869349e-03\n",
    "minPayL3M_Bin_WOE          0.151294   4.293344e-02\n",
    "avgPayL1M_Bin_WOE          0.260946   6.606818e-04\n",
    "的系数为正，需要单独检验\n",
    "'''\n",
    "\n",
    "sm.Logit(y, trainData['maxPayL12M_Bin_WOE']).fit().params  # -0.980206\n",
    "sm.Logit(y, trainData['M0FreqL12M_Bin_WOE']).fit().params  # -1.050918\n",
    "sm.Logit(y, trainData['minPayL6M_Bin_WOE']).fit().params  # -0.812302\n",
    "sm.Logit(y, trainData['increaseUrateL12M_Bin_WOE']).fit().params  #  -0.914707\n",
    "sm.Logit(y, trainData['M0FreqL6M_Bin_WOE']).fit().params  # -1.065785\n",
    "sm.Logit(y, trainData['minPayL3M_Bin_WOE']).fit().params  #  -0.819148\n",
    "sm.Logit(y, trainData['avgPayL1M_Bin_WOE']).fit().params  #  -1.007179\n",
    " \n",
    "# 单独建立回归模型，系数为负，与预期相符，说明仍然存在多重共线性\n",
    "# 下一步，用GBDT跑出变量重要性，挑选出合适的变量\n",
    "clf = ensemble.GradientBoostingClassifier()\n",
    "gbdt_model = clf.fit(X, y)\n",
    "importace = gbdt_model.feature_importances_.tolist()\n",
    "featureImportance = zip(multi_analysis,importace)\n",
    "featureImportanceSorted = sorted(featureImportance, key=lambda k: k[1],reverse=True)\n",
    "\n",
    "\n",
    "\n",
    "# 先假定模型可以容纳5个特征，再逐步增加特征个数，直到有特征的系数为正，或者p值超过0.1\n",
    "n = 5\n",
    "featureSelected = [i[0] for i in featureImportanceSorted[:n]]\n",
    "X_train = X[featureSelected+['intercept']]\n",
    "logit = sm.Logit(y, X_train)\n",
    "logit_result = logit.fit()\n",
    "pvalues = logit_result.pvalues\n",
    "params = logit_result.params\n",
    "fit_result = pd.concat([params,pvalues],axis=1)\n",
    "fit_result.columns = ['coef','p-value']\n",
    " \n",
    "while(n<len(featureImportanceSorted)):\n",
    "    nextVar = featureImportanceSorted[n][0]\n",
    "    featureSelected = featureSelected + [nextVar]\n",
    "    X_train = X[featureSelected+['intercept']]\n",
    "    logit = sm.Logit(y, X_train)\n",
    "    logit_result = logit.fit()\n",
    "    params = logit_result.params\n",
    "    #print(\"current var is \",nextVar,'   ', params[nextVar])\n",
    "    if max(params) < 0:\n",
    "        n += 1\n",
    "    else:\n",
    "        featureSelected.remove(nextVar)\n",
    "        n += 1\n",
    "\n",
    "X_train = X[featureSelected+['intercept']]\n",
    "logit = sm.Logit(y, X_train)\n",
    "logit_result = logit.fit()\n",
    "pvalues = logit_result.pvalues\n",
    "params = logit_result.params\n",
    "fit_result = pd.concat([params,pvalues],axis=1)\n",
    "fit_result.columns = ['coef','p-value']\n",
    "fit_result = fit_result.sort_values(by  = 'p-value')\n",
    "'''\n",
    "                              coef        p-value\n",
    "intercept                -1.809479   0.000000e+00\n",
    "maxDelqL3M_Bin_WOE       -0.762903  2.603323e-192\n",
    "increaseUrateL6M_Bin_WOE -1.194299   4.259502e-68\n",
    "M2FreqL3M_Bin_WOE        -0.684674   1.067350e-64\n",
    "M0FreqL3M_WOE            -0.266852   6.912786e-18\n",
    "avgPayL6M_Bin_WOE        -0.191338   5.979102e-08\n",
    "avgUrateL1M_Bin_WOE      -0.555628   1.473557e-07\n",
    "maxDelqL1M_Bin_WOE       -0.129355   1.536173e-06\n",
    "avgUrateL3M_Bin_WOE      -0.453340   1.364483e-04\n",
    "increaseUrateL3M_WOE     -0.281940   3.123852e-03\n",
    "M1FreqL12M_Bin_WOE       -0.104303   5.702452e-03\n",
    "avgUrateL6M_Bin_WOE      -0.280308   4.784200e-02\n",
    "maxUrateL3M_Bin_WOE      -0.221817   1.254597e-01\n",
    "M1FreqL6M_Bin_WOE        -0.024903   5.002232e-01\n",
    "maxUrateL6M_Bin_WOE      -0.060720   6.897626e-01\n",
    "maxPayL6M_Bin_WOE,maxUrateL6M_Bin_WOE,avgUrateL6M_Bin_WOE,avgPayL12M_Bin_WOE,increaseUrateL12M_Bin_WOE,maxPayL12M_Bin_WOE 的p值大于0.1\n",
    "单独检验显著性\n",
    "'''\n",
    " \n",
    "largePValueVars = pvalues[pvalues>0.1].index\n",
    "for var in largePValueVars:\n",
    "    X_temp = X[[var, 'intercept']]\n",
    "    logit = sm.Logit(y, X_temp)\n",
    "    logit_result = logit.fit()\n",
    "    pvalues = logit_result.pvalues\n",
    "    print(\"The p-value of {0} is {1} \".format(var, str(pvalues[var])))\n",
    "'''\n",
    "The p-value of maxPayL6M_Bin_WOE is 3.94466107162e-137 \n",
    "The p-value of maxUrateL6M_Bin_WOE is 5.83590695685e-35 \n",
    "The p-value of avgUrateL6M_Bin_WOE is 8.17633724544e-37 \n",
    "The p-value of avgPayL12M_Bin_WOE is 1.10614470149e-295 \n",
    "The p-value of increaseUrateL12M_Bin_WOE is 1.9777915301e-57 \n",
    "The p-value of maxPayL12M_Bin_WOE is 1.04348079207e-45 \n",
    "显然，单个变量的p值是显著地。说明任然存在着共线性。\n",
    "'''\n",
    "'''\n",
    "可用L1约束，直到所有变量显著\n",
    "'''\n",
    "\n",
    "\n",
    "X2 = X[featureSelected+['intercept']]\n",
    "for alpha in range(100,0,-1):\n",
    "    l1_logit = sm.Logit.fit_regularized(sm.Logit(y, X2), start_params=None, method='l1', alpha=alpha)\n",
    "    pvalues = l1_logit.pvalues\n",
    "    params = l1_logit.params\n",
    "    if max(pvalues)>=0.1 or max(params)>0:\n",
    "        break\n",
    "\n",
    "bestAlpha = alpha + 1\n",
    "l1_logit = sm.Logit.fit_regularized(sm.Logit(y, X2), start_params=None, method='l1', alpha=bestAlpha)\n",
    "params = l1_logit.params\n",
    "params2 = params.to_dict()\n",
    "featuresInModel = [k for k, v in params2.items() if k!='intercept' and v < -0.0000001]\n",
    "\n",
    "\n",
    "X_train = X[featuresInModel + ['intercept']]\n",
    "logit = sm.Logit(y, X_train)\n",
    "logit_result = logit.fit()\n",
    "trainData['pred'] = logit_result.predict(X_train)\n",
    "\n",
    "### 计算KS值\n",
    "def KS(df, score, target):\n",
    "    '''\n",
    "    :param df: 包含目标变量与预测值的数据集,dataframe\n",
    "    :param score: 得分或者概率,str\n",
    "    :param target: 目标变量,str\n",
    "    :return: KS值\n",
    "    '''\n",
    "    total = df.groupby([score])[target].count()\n",
    "    bad = df.groupby([score])[target].sum()\n",
    "    all = pd.DataFrame({'total':total, 'bad':bad})\n",
    "    all['good'] = all['total'] - all['bad']\n",
    "    all[score] = all.index\n",
    "    all = all.sort_values(by=score,ascending=False)\n",
    "    all.index = range(len(all))\n",
    "    all['badCumRate'] = all['bad'].cumsum() / all['bad'].sum()\n",
    "    all['goodCumRate'] = all['good'].cumsum() / all['good'].sum()\n",
    "    KS = all.apply(lambda x: x.badCumRate - x.goodCumRate, axis=1)\n",
    "    return max(KS)\n",
    "\n",
    "\n",
    "\n",
    "###################################\n",
    "#   5，在测试集上测试逻辑回归的结果   #\n",
    "###################################\n",
    "# 准备WOE编码后的变量\n",
    "modelFeatures = [i.replace('_Bin','').replace('_WOE','') for i in featuresInModel]\n",
    "'''\n",
    "['maxDelqL3M',\n",
    " 'increaseUrateL6M',\n",
    " 'M0FreqL3M',\n",
    " 'avgUrateL1M',\n",
    " 'M2FreqL3M',\n",
    " 'M1FreqL6M',\n",
    " 'avgUrateL3M',\n",
    " 'maxDelqL1M',\n",
    " 'avgPayL6M',\n",
    " 'M1FreqL12M']\n",
    "'''\n",
    "numFeatures = [i for i in modelFeatures if i in numericalFeatures]\n",
    "charFeatures = [i for i in modelFeatures if i in categoricalFeatures]\n",
    " \n",
    "#满足变量的数据预处理\n",
    "testData['maxDelqL1M'] = testData.apply(lambda x: DelqFeatures(x,1,'max delq'),axis=1)\n",
    "testData['maxDelqL3M'] = testData.apply(lambda x: DelqFeatures(x,3,'max delq'),axis=1)\n",
    "# testData['M2FreqL3M'] = testData.apply(lambda x: DelqFeatures(x, 3, 'M2 times'), axis=1)\n",
    "testData['M0FreqL3M'] = testData.apply(lambda x: DelqFeatures(x,3,'M0 times'),axis=1)\n",
    "testData['M1FreqL6M'] = testData.apply(lambda x: DelqFeatures(x, 6, 'M1 times'), axis=1)\n",
    "testData['M2FreqL3M'] = testData.apply(lambda x: DelqFeatures(x, 3, 'M2 times'), axis=1)\n",
    "testData['M1FreqL12M'] = testData.apply(lambda x: DelqFeatures(x, 12, 'M1 times'), axis=1)\n",
    "# testData['maxUrateL6M'] = testData.apply(lambda x: UrateFeatures(x,6,'max utilization rate'),axis = 1)\n",
    "testData['avgUrateL1M'] = testData.apply(lambda x: UrateFeatures(x,1, 'mean utilization rate'),axis=1)\n",
    "testData['avgUrateL3M'] = testData.apply(lambda x: UrateFeatures(x,3, 'mean utilization rate'),axis=1)\n",
    "# testData['avgUrateL6M'] = testData.apply(lambda x: UrateFeatures(x,6, 'mean utilization rate'),axis=1)\n",
    "testData['increaseUrateL6M'] = testData.apply(lambda x: UrateFeatures(x, 6, 'increase utilization rate'),axis=1)\n",
    "# testData['avgPayL3M'] = testData.apply(lambda x: PaymentFeatures(x, 3, 'mean payment ratio'),axis=1)\n",
    "testData['avgPayL6M'] = testData.apply(lambda x: PaymentFeatures(x, 6, 'mean payment ratio'),axis=1)\n",
    " \n",
    "#合并分箱\n",
    "testData['M2FreqL3M_Bin'] = testData['M2FreqL3M'].apply(lambda x: int(x>=1))\n",
    "testData['maxDelqL1M_Bin'] = testData['maxDelqL1M'].apply(lambda x: MergeByCondition(x,['==0','==1','>=2']))\n",
    "testData['maxDelqL3M_Bin'] = testData['maxDelqL3M'].apply(lambda x: MergeByCondition(x,['==0','==1','>=2']))\n",
    " \n",
    "for var in numFeatures:\n",
    "    newBin = var+\"_Bin\"\n",
    "    bin = [list(i.values()) for i in bin_dict if var in i][0][0]\n",
    "    testData[newBin] = testData[var].apply(lambda x: AssignBin(x,bin))\n",
    "\n",
    "finalFeatures = [i+'_Bin' for i in numFeatures] + ['M2FreqL3M_Bin','maxDelqL1M_Bin','maxDelqL3M_Bin','M0FreqL3M']\n",
    "\n",
    "for var in finalFeatures:\n",
    "    var2 = var+\"_WOE\"\n",
    "    testData[var2] = testData[var].apply(lambda x: WOE_IV_dict[var]['WOE'][x])\n",
    "\n",
    "X_test = testData[featuresInModel]\n",
    "X_test['intercept'] = [1]*X_test.shape[0]\n",
    "testData['pred'] = logit_result.predict(X_test)\n",
    "\n",
    "\n",
    "ks = KS(testData, 'pred', 'label')\n",
    "auc = roc_auc_score(testData['label'],testData['pred'])\n",
    "# KS＝64.94%， AUC ＝ 84.43%，都高于30%的标准。因此该模型是可用的。\n",
    "\n",
    "##########################\n",
    "#   6，在测试集上计算分数   #\n",
    "##########################\n",
    "def Prob2Score(prob, basePoint, PDO):\n",
    "    #将概率转化成分数且为正整数\n",
    "    y = np.log(prob/(1-prob))\n",
    "    return int(basePoint+PDO/np.log(2)*(-y))\n",
    " \n",
    "BasePoint, PDO = 500,50\n",
    "testData['score'] = testData['pred'].apply(lambda x: Prob2Score(x, BasePoint, PDO))\n",
    "plt.hist(testData['score'],bins=100)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-05-15T02:56:52.747242Z",
     "start_time": "2019-05-15T02:56:52.669784Z"
    }
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'plt' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-1-d63a0af02bed>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtestData\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'score'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mbins\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m100\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m: name 'plt' is not defined"
     ]
    }
   ],
   "source": [
    "plt.hist(testData['score'],bins=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
